#include <windows.h>
#include <stdio.h>
#include <dsound.h>
#include "wchar.h"
#include "dxdiagcom.h"

#include "dxdiag_res.h"
#include "wine/debug.h"

/*TODO: Add more tests...
 * Software/Hardware 8/16bit Mono/Stereo
 * Detect HARDWARE/SOFTWARE existence
 * How to have the window gain focus?
 * How to actually use these results?
 */

#define MONO 1
#define STEREO 2
#define BIT8 8
#define BIT16 16

/* Constants for wav playback */
#define WAV_HEADER_SIZE 44
#define WAV_SAMPLE_RATE 22050

WINE_DEFAULT_DEBUG_CHANNEL(dxdiag);

typedef struct
{
    int channelCount;
    int bitSampleCount;
    int fileResource;
} DSoundTest_testCase;

DSoundTest_testCase g_testCases[] = { { MONO, BIT8, IDB_MONO8}, {STEREO, BIT8, IDB_STEREO8}, {MONO, BIT16, IDB_MONO16}, {STEREO, BIT16, IDB_STEREO16}, {0,0,0} };

HRESULT DSoundTest_launch(DWORD* tests,HWND hWnd);

HRESULT DSoundTest_test(DWORD* tests,HWND hWnd)
{
    int boxr;
    /* Display initial message box */
    boxr = MessageBox(0,"This will test DirectSound on this device by playing a sound effect using several different audio formats. Okay to proceed?",
        "DirectX Diagnostic Tool", MB_YESNO | MB_APPLMODAL);
    if (boxr != IDYES)
    {
        *tests = 0;
        return S_OK;
    }
    else
    {
        DSoundTest_launch(tests,hWnd);
        return S_OK;
    }
}

HRESULT DSoundTest_playSound(LPDIRECTSOUND8 lpds8, int fileResource, int bitSampleCount, int channelCount)
{
    /* Return value */
    HRESULT hr;

    /* DirectSound specific objects */
    LPDIRECTSOUNDBUFFER lpbuf = NULL;
    LPDIRECTSOUNDBUFFER8 lpbuf8 = NULL;
    DSBUFFERDESC bufd;
    WAVEFORMATEX form;

    /* For buffer loading */
    LPVOID dest = NULL;
    LPVOID src = NULL;
    DWORD length;
    DWORD filesize;
    DWORD dwStatus;
    HRSRC hResource;
    HGLOBAL hResData;

    /* Get the resource */
    hResource = FindResourceA(GetModuleHandle(0), MAKEINTRESOURCE(fileResource), RT_RCDATA);
    if (!hResource)
    {
        WINE_ERR( "FindResourceA() failed!\n");
        abort(); /* FIXME: Handle error */
    }

    filesize = SizeofResource(GetModuleHandle(0), hResource);
    printf("File size is %d bytes\n",filesize);

    /* Set up the WAVEFORMAT */
    memset(&form, 0, sizeof(WAVEFORMATEX));
    form.wFormatTag = WAVE_FORMAT_PCM;
    form.nChannels = channelCount;
    form.wBitsPerSample = bitSampleCount;
    form.nSamplesPerSec = WAV_SAMPLE_RATE;
    form.nBlockAlign = form.nChannels * form.wBitsPerSample / 8;
    form.nAvgBytesPerSec = form.nSamplesPerSec * form.nBlockAlign;

    /* Set up DSBUFFERDESC structure. */
    memset(&bufd, 0, sizeof(DSBUFFERDESC));
    bufd.dwSize = sizeof(DSBUFFERDESC);
    bufd.dwFlags = DSBCAPS_STATIC | DSBCAPS_GLOBALFOCUS | DSBCAPS_LOCDEFER | DSBCAPS_LOCSOFTWARE;
    bufd.dwBufferBytes = filesize + 1;
    bufd.lpwfxFormat = &form;

    /* Create buffer */
    hr = IDirectSound8_CreateSoundBuffer(lpds8,&bufd, &lpbuf, NULL);
    if (SUCCEEDED(hr))
      IDirectSoundBuffer_QueryInterface(lpbuf,&IID_IDirectSoundBuffer8,(LPVOID*) &lpbuf8);
    else
    {
        WINE_ERR("Can't create buffer. Error: %X\n", hr);
        return hr;
    }
    printf("Size of buffer is %d bytes\n",bufd.dwBufferBytes);

    /* Lock the buffer with destination */
    hr = IDirectSoundBuffer8_Lock(lpbuf8,0,0,&dest,&length,
        NULL,NULL,DSBLOCK_ENTIREBUFFER);
    if (FAILED(hr))
    {
        WINE_ERR("Buffer lock failed. Error: %X\n",hr);
        return hr;
    }

    /* Get wave data */
    hResData = LoadResource(GetModuleHandle(0), hResource);
    if (!hResData)
    {
        WINE_ERR( "LoadResource() failed!\n");
        abort();
    }

    /* Get a pointer to beginning of sound data */
    src = LockResource(hResData);
    src = (char*)src + WAV_HEADER_SIZE;

    /* Copy sound data into Direct Sound buffer */
    memcpy(dest,src,filesize);

    /* Unlock the buffer */
    hr = IDirectSoundBuffer8_Unlock(lpbuf8,dest,length,NULL,0);
    if (FAILED(hr))
    {
        WINE_ERR("Buffer unlock failed. Error: %X\n",hr);
        return hr;
    }

    /* Play the buffer */
    hr = IDirectSoundBuffer8_Play(lpbuf8,0,0,DSBPLAY_LOCSOFTWARE);
    if (FAILED(hr))
    {
        WINE_ERR("Buffer playback failed. Error: %X\n",hr);
        return hr;
    }

    /* Wait for the buffer to terminate */
    while (!FAILED(IDirectSoundBuffer8_GetStatus(lpbuf8, &dwStatus)) && (dwStatus & DSBSTATUS_PLAYING));

    /* Release buffers when done */
    IDirectSoundBuffer8_Release(lpbuf8);
    IDirectSoundBuffer_Release(lpbuf);
    FreeResource(hResData);

    return 0;
}

/* Test the Sound */
HRESULT DSoundTest_launch(DWORD* tests, HWND hWnd)
{
    /* Return codes */
    HRESULT hr;
    int boxr;
    WCHAR pBuffer[BUF_SIZE];
    const char *channelStr;
    DSoundTest_testCase *tc = g_testCases;

    /* Create instance of DirectSound */
    LPDIRECTSOUND8 lpds8;
    hr = DirectSoundCreate8(NULL,&lpds8,NULL);
    if (FAILED(hr))
    {
        WINE_ERR("Can't create DirectSound8 Object. Error: %X\n",hr);
        return hr;
    }

    /* Setting normal cooperative level */
    hr = IDirectSound8_SetCooperativeLevel(lpds8,NULL,DSSCL_PRIORITY);
    if (FAILED(hr))
    {
        WINE_ERR( "Can't SetCooperativeLevellevel. Error: %X\n",hr);
        return hr;
    }


    while (tc->channelCount != 0)
    {
        DSoundTest_playSound(lpds8, tc->fileResource, tc->bitSampleCount, tc->channelCount);

        if (tc->channelCount == MONO)
            channelStr = "MONO";
        else if (tc->channelCount == STEREO)
            channelStr = "STEREO";
        else
            channelStr = "UNDEFINED";

        /* Did you hear a sound? */
        wsprintfW(pBuffer, (LPCWSTR)("That sound was:\n\n%s, %d-bit 22kHz, from a Software buffer.\n\n Did you hear the sound play?"),
            channelStr, tc->bitSampleCount);
        boxr = MessageBoxW(NULL, pBuffer, (LPCWSTR)("DirectSound Test"), MB_YESNOCANCEL);

        if (boxr == IDYES)
            *tests = 0;
        else if (boxr == IDCANCEL)
        {
            *tests = 1;
            break;
        }
        else
            *tests = -1;

        printf("Test result: %d\n", *tests);
        tc++;
    }

    /* Release the DirectSound8 Object */
    hr = IDirectSound8_Release(lpds8);
    if (FAILED(hr))
    {
        WINE_ERR("DirectSound object release failed. Error: %X\n",hr);
        return hr;
    }

    /* return status of tests */
    return *tests;
}
